<?php
	/**
	 * PHP代码混淆类
	 *
	 */
	class ENPHPCode
	{
		/**
		 * 混淆项目路径(*必须是绝对路径)
		 *
		 * @var string
		 */
		var $__CODE_PATH;

		/**
		 * 混淆文件后缀设置
		 *
		 * @var array
		 */
		var $__CODE_FILE_TYPE;

		/**
		 * 忽略目录或文件列表
		 *
		 * @var array
		 */
		var $__CODE_IGNORE_FILE;

		/**
		 * 绑定域名设置
		 *
		 * @var string
		 */
		var $__ENCODE_SET_DOMAIN;

		/**
		 * 用户名绑定设置
		 *
		 * @var string
		 */
		var $__ENCODE_SET_USERNAME;

		/**
		 * 程序使用期限设置
		 *
		 * @var string
		 */
		var $__ENCODE_SET_TIMELIMIT;

		/**
		 * 混淆密钥[系统生成]
		 *
		 * @var string
		 */
		var $__ENCODE_SET_KEY;

		/**
		 * 混淆后项目输出路径
		 *
		 * @var string
		 */
		var $__ENCODE_OUT_PATH;

		/**
		 * 混淆文件夹列表
		 *
		 * @var array
		 */
		var $__ENCODE_DIR_LIST = array();

		/**
		 * 混淆文件列表
		 *
		 * @var array
		 */
		var $__ENCODE_FILE_LIST = array();

		/**
		 * 记录需要混淆变量名
		 *
		 * @var array
		 */
		var $__ENCODE_VAR_LIST = array();

		/**
		 * 忽略的变量名
		 *
		 * @var array
		 */
		var $__ENCODE_IGNORE_VAR_LIST = array('$_COOKIE', '$_ENV', '$_FILES', '$_GET', '$_POST', '$_REQUEST', '$_SERVER', '$_SESSION');

		/**
		 * 记录混淆过变量名，用下标对应
		 *
		 * @var array
		 */
		var $__ENCODE_ENVAR_LIST = array();

		/**
		 * 记录需要混淆的函数名
		 *
		 * @var array
		 */
		var $__ENCODE_FUN_LIST = array();

		/**
		 * 记录混淆过的函数名
		 *
		 * @var array
		 */
		var $__ENCODE_ENFUN_LIST = array();

		/**
		 * 混淆文件夹前置标识
		 *
		 * @var string
		 */
		var $__SYS_MARK_ENDir = 'D';

		/**
		 * 混淆文件名前置标识
		 *
		 * @var string
		 */
		var $__SYS_MARK_ENFILE = 'F';

		/**
		 * 混淆变量名前置标识
		 *
		 * @var string
		 */
		var $__SYS_MARK_ENVAR = '';//v49e

		/**
		 * 混淆函数名前置标识
		 *
		 * @var string
		 */
		var $__SYS_MARK_ENFUN = '';//v49f

		/**
		 * 16进制混淆兑换
		 *
		 * @var array
		 */
		var $__SYS_16MARK_ENVAL = array(
				'0'=>'',//v410g
				'1'=>'',//v410h
				'2'=>'',//v410i
				'3'=>'',//v411a
				'4'=>'',//v411b
				'5'=>'',//v411c
				'6'=>'',//v411d
				'7'=>'',//v510f
				'8'=>'',//v510g
				'9'=>'',//v510h
				'a'=>'',//v510i
				'b'=>'',//v511a
				'c'=>'',//v511b
				'd'=>'',//v511c
				'e'=>'',//v511d
				'f'=>'',//v63g
				'A'=>'',//v63h
				'B'=>'',//v63i
				'C'=>'',//v64a
				'D'=>'',//v64b
				'E'=>'',//v64c
				'F'=>''//v64d
			);

		/**
		 * 初始化设置
		 *
		 * @param string $__CODE_PATH
		 * @param array $__CODE_FILE_TYPE
		 * @param array $__CODE_IGNORE_FILE
		 * @param string $__ENCODE_SET_DOMAIN
		 * @param string $__ENCODE_SET_USERNAME
		 * @param string $__ENCODE_SET_TIMELIMIT
		 * @param string $__ENCODE_OUT_PATH
		 */
		function __construct ($__CODE_PATH, $__CODE_FILE_TYPE, $__CODE_IGNORE_FILE=array(), $__ENCODE_SET_DOMAIN, $__ENCODE_SET_USERNAME, $__ENCODE_SET_TIMELIMIT=NULL, $__ENCODE_OUT_PATH = './out')
		{
			/* 过滤提交参数 */
			$__CODE_PATH = trim($__CODE_PATH);
			$__ENCODE_SET_DOMAIN = trim($__ENCODE_SET_DOMAIN);
			$__ENCODE_SET_USERNAME = trim($__ENCODE_SET_USERNAME);
			$__ENCODE_SET_TIMELIMIT = trim($__ENCODE_SET_TIMELIMIT);
			$__ENCODE_OUT_PATH = trim($__ENCODE_OUT_PATH);

			/* 检测参数 */
			if(empty($__CODE_PATH) || !is_dir($__CODE_PATH)){ die('__CODE_PATH 不能为空且是有效目录！'); }
			if(empty($__CODE_FILE_TYPE) || !is_array($__CODE_FILE_TYPE)){ die('__CODE_FILE_TYPE 不能为空且只能是数组！'); }
			if(!empty($__CODE_IGNORE_FILE) && !is_array($__CODE_IGNORE_FILE)){ die('__CODE_IGNORE_FILE 只能是数组！'); }
			if(empty($__ENCODE_SET_DOMAIN)){ die('__ENCODE_SET_DOMAIN 不能为空！'); }
			if(empty($__ENCODE_SET_USERNAME)){ die('__ENCODE_SET_USERNAME 不能为空！'); }
			if(empty($__ENCODE_OUT_PATH)){ die('__ENCODE_OUT_PATH 不能为空！'); }

			/* 输出目录不存在则建立 */
			if(!is_dir($__ENCODE_OUT_PATH))
			{
				if(!mkdir($__ENCODE_OUT_PATH,0777))
				{
					die('输出目录建立失败。');
				}
			}
			
			/* 检测输出目录权限 */
			if(!is_writable($__ENCODE_OUT_PATH))
			{
				die('输出目录不可写入。');
			}

			/* 时间检测 */
			if(!empty($__ENCODE_SET_TIMELIMIT))
			{
				preg_match("/^[0-9]{4}-[0-9]{2}$/", $__ENCODE_SET_TIMELIMIT, $result);
				if(empty($result))
				{
					preg_match("/^[0-9]{4}$/", $__ENCODE_SET_TIMELIMIT, $result);
					if(empty($result)){ die('__ENCODE_SET_TIMELIMIT 字符串格式错误，只能是 YYYY-MM 或 YYYY！'); }
				}
			}

			/* 设置 */
			$this->__CODE_PATH = $__CODE_PATH;
			$this->__CODE_FILE_TYPE = $__CODE_FILE_TYPE;
			$this->__CODE_IGNORE_FILE = $__CODE_IGNORE_FILE;
			$this->__ENCODE_SET_DOMAIN = $__ENCODE_SET_DOMAIN;
			$this->__ENCODE_SET_USERNAME = $__ENCODE_SET_USERNAME;
			$this->__ENCODE_SET_TIMELIMIT = $__ENCODE_SET_TIMELIMIT;
			$this->__ENCODE_OUT_PATH = $__ENCODE_OUT_PATH;
			$this->Make_ENCODE_KEY();
		}

		/**
		 * 再度混淆变量名
		 *
		 * @param string &$STR
		 */
		function Make_ENVAL ( $STR )
		{
			$NEWSTR = '';
			for($i=0;$i<strlen($STR);$i++){
				$NEWSTR .= $this->__SYS_16MARK_ENVAL[$STR[$i]];
			}
			return $NEWSTR;
		}

		/**
		 * 获取加密用的混淆密钥
		 *
		 * @return string
		 */
		function Make_ENCODE_KEY ()
		{
			$this->__ENCODE_SET_KEY = substr(md5($this->__ENCODE_SET_DOMAIN.$this->__ENCODE_SET_USERNAME), 12);
			if(!empty($this->__ENCODE_SET_TIMELIMIT))
			{
				$this->__ENCODE_SET_KEY .= substr(md5($this->__ENCODE_SET_TIMELIMIT), -10);
			}
			else
			{
				$this->__ENCODE_SET_KEY .= '0000000000';
			}
			$this->__ENCODE_SET_KEY = $this->Make_ENVAL($this->__ENCODE_SET_KEY);
		}

		/**
		 * 创建混淆文件索引
		 *
		 * @param string $SUBDIR
		 */
		function Create_ENCODE_FILE_LIST ($SUBDIR = NULL)
		{
			$DirPath = $this->__ENCODE_OUT_PATH;
			if(!empty($SUBDIR)) $DirPath .= $SUBDIR;
			if($DH = opendir($DirPath))
			{
				while($filename = readdir($DH))
				{
					if($filename == '.' || $filename == '..' || in_array($filename, $this->__CODE_IGNORE_FILE)){ continue; }
					if(is_dir($DirPath.'/'.$filename))
					{
						$this->__ENCODE_DIR_LIST[] = $DirPath.'/'.$filename;
						$this->Create_ENCODE_FILE_LIST($SUBDIR.'/'.$filename);
					}
					$this->__ENCODE_FILE_LIST[] = $DirPath.'/'.$filename;
				}
				closedir($DH);
			}
			else
			{
				die("{$DirPath} 目录读取失败！");
			}
		}

		/**
		 * 创建可替换数据列表
		 *
		 */
		function Create_ENCODE_ENDATA ()
		{
			foreach($this->__ENCODE_FILE_LIST as $filename)
			{
				if(!in_array(array_pop(explode('.', $filename)), $this->__CODE_FILE_TYPE)){ continue; }

				$file_content = $this->Filter_FILE_CONTENT(file_get_contents($filename));

				/* 获取可替换变量 */
				preg_match_all('/(\$[\w]+)\W*/', $file_content,$result);
				foreach($result[1] as $key => $varname)
				{
					if(!in_array($varname, $this->__ENCODE_VAR_LIST) && !in_array($varname, $this->__ENCODE_IGNORE_VAR_LIST))
					{
						$this->__ENCODE_VAR_LIST[] = $varname;
						$this->__ENCODE_ENVAR_LIST[] = '$'.$this->__SYS_MARK_ENVAR.$this->Make_ENVAL(dechex($key));
					}
				}
				arsort($this->__ENCODE_VAR_LIST);
				arsort($this->__ENCODE_ENVAR_LIST);
				
				/* 获取可替换函数 */
				preg_match_all('/function[\s]+(\w+)\s*\(/', $file_content, $result);
				foreach($result[1] as $key => $funname)
				{
					if(!in_array($funname, $this->__ENCODE_VAR_LIST))
					{
						$this->__ENCODE_FUN_LIST[] = $funname;
						$this->__ENCODE_ENFUN_LIST[] = $this->__SYS_MARK_ENFUN.$this->Make_ENVAL(dechex($key));
					}
				}
				arsort($this->__ENCODE_FUN_LIST);
				arsort($this->__ENCODE_ENFUN_LIST);
			}
		}

		/**
		 * 复制要混淆的文件
		 *
		 * @param string $SUBDIR
		 */
		function COPY_RUN_FILE($SUBDIR = NULL)
		{
			$SourceDirPath = $this->__CODE_PATH;
			$TargetDirPath = $this->__ENCODE_OUT_PATH;
			if(!empty($SUBDIR))
			{
				$SourceDirPath .= $SUBDIR;
				$TargetDirPath .= $SUBDIR;
			}
			if($DH = opendir($SourceDirPath))
			{
				while($filename = readdir($DH))
				{
					if($filename == '.' || $filename == '..' || in_array($filename, $this->__CODE_IGNORE_FILE)){ continue; }
					if(is_dir($SourceDirPath.'/'.$filename))
					{
						if(!is_dir($TargetDirPath.'/'.$filename)){ mkdir($TargetDirPath.'/'.$filename, 0777); }
						$this->COPY_RUN_FILE($SUBDIR.'/'.$filename);
					}
					if(is_file($SourceDirPath.'/'.$filename))
					{
						if(is_readable($SourceDirPath.'/'.$filename))
						{
							copy($SourceDirPath.'/'.$filename, $TargetDirPath.'/'.$filename);
						}
						else 
						{
							echo "无法读取：".$SourceDirPath.'/'.$filename."<br />\n";
						}
					}
				}
				closedir($DH);
			}
			else
			{
				die("{$SourceDirPath} 目录读取失败！");
			}
		}

		function Start_ENPHPCode($SUBDIR = NULL)
		{
			$DirPath = $this->__ENCODE_OUT_PATH;
			if(!empty($SUBDIR)) $DirPath .= $SUBDIR;
			if($DH = opendir($DirPath))
			{
				while($filename = readdir($DH))
				{
					if($filename == '.' || $filename == '..' || in_array($filename, $this->__CODE_IGNORE_FILE)){ continue; }
					if(is_dir($DirPath.'/'.$filename)){ $this->Start_ENPHPCode($SUBDIR.'/'.$filename); }
					if(is_file($DirPath.'/'.$filename))
					{
						if(!in_array(array_pop(explode('.', $filename)), $this->__CODE_FILE_TYPE)){ continue; }
						$file_content = file_get_contents($DirPath.'/'.$filename);
						foreach($this->__ENCODE_VAR_LIST as $key=>$varname)
						{
							while(strpos($file_content, $varname))
							{
								$file_content = preg_replace('/\\'.$varname.'(\W*)/', $this->__ENCODE_ENVAR_LIST[$key].'\1', $file_content);
							}
						}
						foreach($this->__ENCODE_FUN_LIST as $key=>$funname)
						{
							while(preg_match('/'.$funname.'\s*\(/i', $file_content))
							{
								$file_content = preg_replace('/'.$funname.'\s*\((\W*)/i', $this->__ENCODE_ENFUN_LIST[$key].'(\1', $file_content);
							}
						}
						$file_content = str_replace("\t", '', $file_content);
						$file_content = preg_replace('/\s+\/\/[^\n]*|\/\*([^\*^\/]*|[\*^\/*]*|[^\**\/]*)*\*\//', '', $file_content);
						$file_content = preg_replace('/\s(?=\s)/', '', $file_content);
						$file_content = str_replace(array("\t","\r"), '', $file_content);
						while(strpos($file_content, "\n"))
						{
							$file_content = str_replace("\n", ' ', $file_content);
						}
						file_put_contents($DirPath.'/'.$filename, $file_content);
					}
				}
				closedir($DH);
			}
			else
			{
				die("{$DirPath} 目录读取失败！");
			}
		}

		/**
		 * 过滤文件中的特殊字符，方便处理
		 *
		 * @param string $filecontent
		 * @return string
		 */
		function Filter_FILE_CONTENT($filecontent)
		{
			$filecontent = str_replace(array('<?php', '?>', "\t", "\n"), '', $filecontent);
			$filecontent = preg_replace('/\<script(.*)\<\/script\>/i', '', $filecontent);
			return $filecontent;
		}

		/**
		 * 执行混淆
		 *
		 */
		function Run ()
		{
			set_time_limit(0);
			$this->COPY_RUN_FILE();
			$this->Create_ENCODE_FILE_LIST();
			$this->Create_ENCODE_ENDATA();
			$this->Start_ENPHPCode();
		}
	}
?>